package common;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtils extends AbstractSerializer {
    private final char separator = ':';
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Resource(name = "redisTemplateForHash")
	private RedisTemplate<String, Object> redisTemplateForHash;

    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value, long expireTime) {
        try {
            if (expireTime > 0) {
                redisTemplate.opsForValue().set(key, value.toString(), expireTime, TimeUnit.SECONDS);
            }else {
                redisTemplate.opsForValue().set(key, value.toString());
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }
    /**
     * @param c
     * @param key
     * @param value
     * @param expireTime 单位秒
     * @return
     */
    public boolean set(Class c, String key, Object value, long expireTime) {
        return this.redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] keyBytes = serializeKey(c.getSimpleName() + separator + key);
                return conn.set(keyBytes, serialize(value), Expiration.seconds(expireTime), RedisStringCommands.SetOption.upsert());
            }
        });
    }
    /**
     * @param c
     * @param key
     * @return
     */
    public boolean del(Class c, String key) {
        return this.redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] keyBytes = serializeKey(c.getSimpleName() + separator + key);
                Long del = conn.del(keyBytes);
                return 1 == del;
            }
        });
    }

    /**
     * Add {@code value} to a sorted set at {@code key}, or update its {@code score} if it already exists.
     *
     * @param c
     * @param key
     * @param score
     * @param value
     * @return 如果value不存在，添加成功则返回true，否则执行对score执行更新操作，并且返回false
     */
    public boolean zAdd(Class c, String key, double score, String value) {
        return this.redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] keyBytes = serializeKey(c.getSimpleName() + separator + key);
                return conn.zAdd(keyBytes, score, serialize(value));
            }
        });
    }

    /**
     * Remove {@code value} from sorted set. Return number of removed elements.
     *
     * @param c
     * @param key
     * @param value
     * @return 被成功移除的成员的数量，不包括被忽略的成员
     */
    public Long zRem(Class c, String key, String value) {
        if (StringUtils.isBlank(value)) {
            return null;
        }
        String[] values = {value};
        return this.zRem(c, key, values);
    }

    /**
     * Remove {@code values} from sorted set. Return number of removed elements.
     *
     * @param c
     * @param key
     * @param values
     * @return 被成功移除的成员的数量，不包括被忽略的成员
     */
    public Long zRem(Class c, String key, String... values) {
        if (Objects.isNull(values) || values.length == 0) {
            return null;
        }
        return this.redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] keyBytes = serializeKey(c.getSimpleName() + separator + key);
                byte[][] valueByts = new byte[values.length][];
                for (int i = 0; i < values.length; i++) {
                    valueByts[i] = serialize(values[i]);
                }
                return conn.zRem(keyBytes, valueByts);
            }
        });
    }

    /**
     * Remove elements with scores between {@code min} and {@code max} from sorted set with {@code key}.
     * @param c
     * @param key
     * @param min 包括最小值
     * @param max 包括最大值
     * @return 返回移除的数量
     */
    public Long zRemRangeByScore(Class c, String key, double min, double max) {
        return this.redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] keyBytes = serializeKey(c.getSimpleName() + separator + key);
                return conn.zRemRangeByScore(keyBytes,  min,  max);
            }
        });
    }

    /**
     * Increment the score of element with {@code value} in sorted set by {@code increment}.
     *
     * @param c
     * @param key
     * @param increment
     * @param value
     * @return 返回操作后的score分值
     */
    public Double zIncrBy(Class c, String key, double increment, String value) {
        return this.redisTemplate.execute(new RedisCallback<Double>() {
            @Override
            public Double doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] keyBytes = serializeKey(c.getSimpleName() + separator + key);
                return conn.zIncrBy(keyBytes, increment, serialize(value));
            }
        });
    }

    /**
     * Get the size of sorted set with {@code key}.
     *
     * @param c
     * @param key
     * @return
     */
    public Long zCard(Class c, String key) {
        return this.redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] keyBytes = serializeKey(c.getSimpleName() + separator + key);
                return conn.zCard(keyBytes);
            }
        });
    }

    /**
     * Get the score of element with {@code value} from sorted set with key {@code key}.
     * @param c
     * @param key
     * @param value
     * @return
     */
    public Double zScore(Class c, String key, String value) {
        return this.redisTemplate.execute(new RedisCallback<Double>() {
            @Override
            public Double doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] keyBytes = serializeKey(c.getSimpleName() + separator + key);
                return conn.zScore(keyBytes, serialize(value));
            }
        });
    }

    /**
     * Get elements between {@code start} and {@code end} from sorted set
     * start<=index<=end
     *
     * @param c
     * @param key
     * @param start 以 0 表示有序集第一个成员，以 1 表示有序集第二个成员，以此类推，start 超出最小index，不会报错
     * @param end   你也可以使用负数下标，以 -1 表示最后一个成员， -2 表示倒数第二个成员，以此类推,end 超出最大index，不会报错
     * @return 返回指定索引中的元素value集合
     */
    public Set<String> zRange(Class c, String key, long start, long end) {
        return this.redisTemplate.execute(new RedisCallback<Set>() {
            @Override
            public Set doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] keyBytes = serializeKey(c.getSimpleName() + separator + key);
                Set<byte[]> bytes = conn.zRange(keyBytes, start, end);
                Set<String> valueSets = new HashSet<>();
                if (Objects.isNull(bytes) || bytes.size() == 0) {
                    return valueSets;
                }
                Iterator<byte[]> iterator = bytes.iterator();
                while (iterator.hasNext()) {
                    valueSets.add(deserialize(String.class, iterator.next()));
                }
                return valueSets;
            }
        });
    }

    /**
     * Get elements where score is between {@code min} and {@code max} from sorted set.
     * @param c
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<String> zRangeByScore(Class c, String key, double min, double max) {
        return this.redisTemplate.execute(new RedisCallback<Set>() {
            @Override
            public Set doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] keyBytes = serializeKey(c.getSimpleName() + separator + key);
                Set<byte[]> bytes = conn.zRangeByScore(keyBytes, min, max);
                Set<String> valueSets = new HashSet<>();
                if (Objects.isNull(bytes) || bytes.size() == 0) {
                    return valueSets;
                }
                Iterator<byte[]> iterator = bytes.iterator();
                while (iterator.hasNext()) {
                    valueSets.add(deserialize(String.class, iterator.next()));
                }
                return valueSets;
            }
        });
    }

    /**
     * Get set of {@link RedisZSetCommands.Tuple}s between {@code start} and {@code end} from sorted set.
     * start<=index<=end
     *
     * @param c
     * @param key
     * @param start 以 0 表示有序集第一个成员，以 1 表示有序集第二个成员，以此类推，start 超出最小index，不会报错
     * @param end   你也可以使用负数下标，以 -1 表示最后一个成员， -2 表示倒数第二个成员，以此类推,end 超出最大index，不会报错
     * @return 返回指定索引中的元素value和score集合
     */
    public Map<String, Double> zRangeWithScores(Class c, String key, long start, long end) {
        return this.redisTemplate.execute(new RedisCallback<Map>() {
            @Override
            public Map doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] keyBytes = serializeKey(c.getSimpleName() + separator + key);
                Set<RedisZSetCommands.Tuple> tuples = conn.zRangeWithScores(keyBytes, start, end);
                Map<String, Double> valueScoreMaps = new HashMap<>();
                if (Objects.isNull(tuples) || tuples.size() == 0) {
                    return valueScoreMaps;
                }
                Iterator<RedisZSetCommands.Tuple> iterator = tuples.iterator();
                while (iterator.hasNext()) {
                    RedisZSetCommands.Tuple next = iterator.next();
                    valueScoreMaps.put(deserialize(String.class, next.getValue()), next.getScore());
                }
                return valueScoreMaps;
            }
        });
    }




    /**
     * 设置的信息不会自动失效
     *
     * @param c
     * @param key
     * @param value
     * @return
     */
    public boolean set(Class c, String key, Object value) {
        return this.redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] keyBytes = serializeKey(c.getSimpleName() + separator + key);
                return conn.set(keyBytes, serialize(value));
            }
        });
    }

    /**
     * @param c
     * @param key
     * @return
     */
    public <T> T get(Class c, String key, Class<T> type) {
        return this.redisTemplate.execute(new RedisCallback<T>() {
            @Override
            public T doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] bytes = conn.get(serializeKey(c.getSimpleName() + separator + key));
                return deserialize(type, bytes);
            }
        });
    }

    public <T> List<T> getList(Class c, String key, Class<T> type) {
        return this.redisTemplate.execute(new RedisCallback<List<T>>() {
            @Override
            public List<T> doInRedis(RedisConnection conn)
                    throws DataAccessException {
                byte[] bytes = conn.get(serializeKey(c.getSimpleName() + separator + key));
                return deserializeArray(type, bytes);
            }
        });
    }


    /**
     * 查找匹配key
     *
     * @param pattern key
     * @return /
     */
    public List<String> scan(Class c,String pattern) {
        ScanOptions options = ScanOptions.scanOptions().match(c.getSimpleName() + separator + pattern).build();
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisConnection rc = Objects.requireNonNull(factory).getConnection();
        Cursor<byte[]> cursor = rc.scan(options);
        List<String> result = new ArrayList<>();
        while (cursor.hasNext()) {
            result.add(new String(cursor.next()));
        }
        try {
            RedisConnectionUtils.releaseConnection(rc, factory);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * HashGet
     *
     * @param key  键 不能为 null
     * @param item 项 不能为 null
     * @return 值
     */
    public Object hGet(String key, String item) {
        return redisTemplateForHash.opsForHash().get(key, item);
    }

    /**
     * 获取 hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hMGet(String key) {
        return redisTemplateForHash.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public Boolean hMSet(String key, Map<String, Object> map) {
        try {
            redisTemplateForHash.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public Boolean hMSet(String key, Map<String, Object> map, Long time) {
        try {
            redisTemplateForHash.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public Boolean hSet(String key, String item, Object value) {
        try {
            redisTemplateForHash.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public Boolean hSet(String key, String item, Object value, Long time) {
        try {
            redisTemplateForHash.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为 null
     * @param item 项 可以使多个不能为 null
     */
    public void hDel(String key, Object... item) {
        redisTemplateForHash.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为 null
     * @param item 项 不能为 null
     * @return true 存在 false不存在
     */
    public Boolean hHasKey(String key, String item) {
        return redisTemplateForHash.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return Double
     */
    public Double hIncr(String key, String item, Double by) {
        return redisTemplateForHash.opsForHash().increment(key, item, by);
    }

    /**
     * 获取递增的序列号
     *
     * @param c
     * @param key
     * @return
     */
    public String inc(Class c,String key) {
        String s = c.getSimpleName() + separator + key;
        //序列号前缀加特定标识，如系统模块名之类的 防止重复
        String increResult = null;
        try {
            //如果该key不存在 会自动创建，值为第二个参数delta
            Long increNum = redisTemplateForHash.opsForValue().increment(s, 1);
            // 小于6位补位到6位
            increResult = String.format("%1$06d", increNum);
        } catch (Exception e) {
           e.printStackTrace();
            increResult = get12UUID();
        }
        return increResult;
    }

    /**
     * 获得12个长度的十六进制的UUID
     * @return UUID
     */
    public static String get12UUID(){
        UUID id=UUID.randomUUID();
        String[] idd=id.toString().split("-");
        return idd[0]+idd[1];
    }

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return Boolean
     */
    public Boolean expire(String key, Long time) {
        try {
            if (time > 0) {
                redisTemplateForHash.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 入队
     *
     * @param key  键
     * @param item 项
     * @return Boolean
     */
    public Boolean queuePush(String key,Object item) {
        try {
            redisTemplateForHash.opsForList().leftPush(key, item);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 出队
     *
     * @param key  键
     * @return Boolean
     */
    public Object queuePop(String key) {
        try {
            return redisTemplateForHash.opsForList().rightPop(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

}
